package com.zds.ftp.controller;

import com.zds.ftp.form.QueryFileForm;
import com.zds.ftp.result.FileItem;
import com.zds.ftp.result.Folder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ZeroCopyHttpOutputMessage;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
@Slf4j
public class IndexController {
    @Value("${user.home}")
    private String home;

    @GetMapping("/disk")

    public Mono<Folder> index(QueryFileForm form) throws Exception {

        File file = new File(StringUtils.hasText(form.getPath()) ? form.getPath() : home);

        if (!file.exists()) {
            throw new FileNotFoundException("目录不存在");
        }

        File[] files = file.listFiles();

        Folder folder = new Folder();
        folder.setCurrentPath(file.getPath());

        if (files == null) {
            return Mono.just(folder);
        }

        Stream<File> stream = Arrays.stream(files);

        if (form.isHiddenFile()) {
            stream = stream.filter(x -> !x.isFile());
        }
        if (form.isHiddenFolder()) {
            stream = stream.filter(x -> !x.isDirectory());
        }
        if (form.isIgnoreHidden()) {
            stream = stream.filter(x -> !x.isHidden());
        }
        if (form.isAsc()) {
            stream = stream.sorted(Comparator.comparing(File::lastModified).reversed());
        }

        List<FileItem> listFiles = stream
                .map(FileItem::of)
                .collect(Collectors.toList());


        folder.setFiles(listFiles);


        return Mono.just(folder);
    }

    @PostMapping("/upload")
    public Mono<Void> upload(@RequestPart("file") FilePart files) {
        File file = new File(home + File.separator
                + "selfUploadFolder" + File.separator + files.filename());
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        return files.transferTo(file);
    }

    @GetMapping("/download")
    public Mono<Void> download(String path, ServerHttpResponse response) {
        if (!StringUtils.hasText(path)) {
            return Mono.error(() -> new Exception("参数不能为空"));
        }
        log.info("执行下载： {}", path);
        File file = new File(path);

        if (!file.exists()) {
            return Mono.error(() -> new Exception("文件不存在"));
        }

        ZeroCopyHttpOutputMessage message = (ZeroCopyHttpOutputMessage) response;
        HttpHeaders headers = message.getHeaders();

        if (!file.isDirectory()) {
            headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            String name = new String(file.getName().getBytes(), StandardCharsets.ISO_8859_1);
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + name);

            return message.writeWith(file, 0, file.length());
        }

        Mono<byte[]> zip = Mono.fromCallable(() -> {
            try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
                 ZipOutputStream zipOutputStream = new ZipOutputStream(bout);
                 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(zipOutputStream)) {
                zip(zipOutputStream, file, file.getName(), bufferedOutputStream);
                bufferedOutputStream.flush();
                zipOutputStream.finish();
                return bout.toByteArray();
            } catch (Exception e) {
                return new byte[0];
            }
        }).subscribeOn(Schedulers.boundedElastic());

        return zip.flatMap(y -> {
            String name = new String((file.getName() + ".zip").getBytes(), StandardCharsets.ISO_8859_1);
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + name);
            return message.writeWith(Mono.just(new DefaultDataBufferFactory().wrap(y)));
        });
    }

    private void zip(ZipOutputStream zout, File target, String name, BufferedOutputStream bos) throws IOException {
        //判断是不是目录
        if (target.isDirectory()) {
            File[] files = target.listFiles();
            //空目录
            if (files == null) {
                return;
            }
            if (files.length == 0) {
                zout.putNextEntry(new ZipEntry(name + File.separator));
            }
            for (File f : files) {
                //递归处理
                zip(zout, f, name + File.separator + f.getName(), bos);
            }
        } else {
            zout.putNextEntry(new ZipEntry(name));
            try (InputStream inputStream = new FileInputStream(target);
                 BufferedInputStream bis = new BufferedInputStream(inputStream)) {
                byte[] bytes = new byte[4096];
                int len;
                while ((len = bis.read(bytes)) != -1) {
                    bos.write(bytes, 0, len);
                }
                bos.flush();
            }
        }
    }
}
